package com.dss.sdk.utils.http;

import com.dss.sdk.config.HttpConfig;
import com.dss.sdk.constants.Constants;
import com.dss.sdk.exception.ApiException;
import com.dss.sdk.file.FileItem;
import com.dss.sdk.response.DssEntityResponse;
import com.dss.sdk.response.DssResponse;
import com.dss.sdk.utils.string.StrUtil;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.HttpsSupport;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.util.Timeout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * @author Fadada
 * 2021/9/8 16:09:38
 */
public class HttpUtil {
    private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);
    public static HttpConfig httpConfig;
    private static int DEFAULT_MAX_PER_ROUTE = 350;
    private static int DEFAULT_SOCKET_MAX_TOTAL = 400;
    private static volatile CloseableHttpClient closeableHttpClient;

    private static Lock lock = new ReentrantLock();
    /**
     * 忽略SSL检查
     */
    private static boolean ignoreSSLCheck = true;
    /**
     * 忽略HOST检查
     */
    private static boolean ignoreHostCheck = true;

    private HttpUtil() {
    }

    public static void setIgnoreSSLCheck(boolean ignoreSSLCheck) {
        HttpUtil.ignoreSSLCheck = ignoreSSLCheck;
    }

    public static void setIgnoreHostCheck(boolean ignoreHostCheck) {
        HttpUtil.ignoreHostCheck = ignoreHostCheck;
    }

    private static HttpGet getHttpGet(String url, Map<String, String> params, String encode) {
        StringBuilder buf = new StringBuilder(url);
        if (params != null) {
            // 地址增加?或者&
            String flag = (url.indexOf('?') == -1) ? "?" : "&";
            // 添加参数
            for (Entry<String, String> entry : params.entrySet()) {
                buf.append(flag);
                buf.append(entry.getKey());
                buf.append("=");
                try {
                    String param = entry.getValue();
                    if (param == null) {
                        param = "";
                    }
                    buf.append(URLEncoder.encode(param, encode));
                } catch (UnsupportedEncodingException e) {
                    log.error("URLEncoder Error,encode=" + encode + ",param=" + entry.getValue(), e);
                }
                flag = "&";
            }
        }
        return new HttpGet(buf.toString());
    }

    /**
     * 上传文件 post
     *
     * @param url    请求地址
     * @param params 请求参数
     * @param files  请求文件
     * @return HttpPost
     */
    private static HttpPost getHttpPost(String url, Map<String, String> params, Map<String, FileItem> files) throws ApiException {
        HttpPost httpPost = new HttpPost(url);
        MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
        if (files != null && !files.isEmpty()) {
            for (Entry<String, FileItem> kv : files.entrySet()) {
                FileItem fileItem = kv.getValue();
                if (!fileItem.isValid()) {
                    log.error("FileItem is invalid");
                    throw new ApiException("FileItem is invalid");
                }
                try {
                    multipartEntityBuilder.addBinaryBody(kv.getKey(), fileItem.getFile(),
                            ContentType.create(fileItem.getMimeType()), fileItem.getFileName());
                } catch (IOException e) {
                    log.error("FileItem ContentType is invalid");
                    throw new ApiException("FileItem ContentType is invalid ");
                }
            }
        }
        if (params != null && !params.isEmpty()) {
            for (Entry<String, String> entry : params.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                if (value != null) {
                    multipartEntityBuilder.addTextBody(key, value, ContentType.TEXT_PLAIN.withCharset(Constants.CHARSET_UTF8));
                }
            }
            multipartEntityBuilder.setMode(HttpMultipartMode.EXTENDED);
        }
        HttpEntity httpEntity = multipartEntityBuilder.build();
        httpPost.setEntity(httpEntity);
        return httpPost;
    }

    /**
     * post请求
     *
     * @param url    请求路径
     * @param params 请求参数
     * @return HttpPost
     */
    private static HttpPost getHttpPost(String url, String params, String charset) {
        HttpPost httpPost = new HttpPost(url);
        if (StrUtil.isNotBlank(params)) {
            StringEntity httpEntity = new StringEntity(params, ContentType.APPLICATION_JSON.withCharset(charset));
            httpPost.setEntity(httpEntity);
        }
        return httpPost;
    }

    public static DssResponse post(String url, Map<String, String> reqHeader, Object params,
                                   Map<String, FileItem> files) throws ApiException {
        try {
            // 初始化httpClient
            CloseableHttpClient httpClient = getHttpClient(httpConfig);
            // 创建http请求 设置请求参数
            HttpPost httpPost;
            if (files == null || files.isEmpty()) {
                httpPost = getHttpPost(url, (String) params, Constants.CHARSET_UTF8);
            } else {
                httpPost = getHttpPost(url, (Map<String, String>) params, files);
            }
            httpPost.setConfig(getRequestConfig(httpConfig));
            return executeHttpRequest(httpClient, httpPost, reqHeader);
        } catch (Exception e) {
            log.error("url=[{}] http请求失败：{}", url, e.getMessage(), e);
            throw new ApiException("请求失败");
        }
    }

    /**
     * 获取client
     *
     * @param httpConfig http配置
     * @return CloseableHttpClient client
     */
    public static CloseableHttpClient getHttpClient(HttpConfig httpConfig) throws ApiException {
        if (closeableHttpClient != null) {
            return closeableHttpClient;
        }
        lock.lock();
        try {
            if (closeableHttpClient != null) {
                return closeableHttpClient;
            }
            RegistryBuilder<ConnectionSocketFactory> builder = RegistryBuilder.create();
            // 注册 http socket factory
            builder.register("http", PlainConnectionSocketFactory.getSocketFactory());
            // 注册 https socket factory
            if (httpConfig != null && Boolean.TRUE.equals(httpConfig.getProxyFlag())) {
                builder.register("https", SSLConnectionSocketFactory.getSocketFactory());
            } else {
                HostnameVerifier hostnameVerifier = HttpsSupport.getDefaultHostnameVerifier();
                SSLContext sslContext;
                try {
                    sslContext = SSLContext.getInstance("TLSv1.2");
                    sslContext.init(null, ignoreSSLCheck ? new TrustManager[]{new TrustAllTrustManager()} : null, new SecureRandom());
                    hostnameVerifier = (hostname, session) -> true;
                } catch (Exception e) {
                    log.error("Failed to initialize SSL context", e);
                    sslContext = SSLContext.getInstance("TLSv1.2");
                }
                if (ignoreHostCheck) {
                    hostnameVerifier = (hostname, session) -> true;
                }
                builder.register("https", new SSLConnectionSocketFactory(sslContext, hostnameVerifier));
            }
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(builder.build());
            Integer defaultMaxPerRoute = DEFAULT_MAX_PER_ROUTE;
            Integer defaultSocketMaxTotal = DEFAULT_SOCKET_MAX_TOTAL;
            if (httpConfig != null && httpConfig.getDefaultMaxPerRoute() != null && httpConfig.getDefaultMaxPerRoute() > 0) {
                defaultMaxPerRoute = httpConfig.getDefaultMaxPerRoute();
            }
            if (httpConfig != null && httpConfig.getDefaultSocketMaxTotal() != null && httpConfig.getDefaultSocketMaxTotal() > 0) {
                defaultSocketMaxTotal = httpConfig.getDefaultSocketMaxTotal();
            }
            connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
            connectionManager.setMaxTotal(defaultSocketMaxTotal);
            connectionManager.setValidateAfterInactivity(Timeout.of(3000, TimeUnit.MILLISECONDS));
            connectionManager.closeExpired();
            closeableHttpClient = HttpClients.custom().setConnectionManager(connectionManager).build();
        } catch (Exception e) {
            log.error("HttpClient生成失败：{}", e.getMessage(), e);
            throw new ApiException("HttpClient生成失败");
        } finally {
            lock.unlock();
        }
        return closeableHttpClient;
    }


    public static DssResponse get(String url, Map<String, String> reqHeader, Map<String, String> params) throws ApiException {
        // 初始化httpClient
        CloseableHttpClient httpClient = getHttpClient(httpConfig);
        // 创建http请求 设置请求参数
        HttpGet httpGet = getHttpGet(url, params, "UTF-8");
        // 设置超时时间、代理配置
        httpGet.setConfig(getRequestConfig(httpConfig));
        return executeHttpRequest(httpClient, httpGet, reqHeader);
    }


    public static DssResponse executeHttpRequest(CloseableHttpClient client, HttpUriRequest request, Map<String, String> reqHeader) throws ApiException {
        CloseableHttpResponse response = null;
        try {
            if (reqHeader != null && !reqHeader.isEmpty()) {
                for (Entry<String, String> entry : reqHeader.entrySet()) {
                    request.addHeader(entry.getKey(), entry.getValue());
                }
            }
            response = client.execute(request);
            reqesutAndResponseLog(request, response);
            DssResponse httpInfoRes = DssResponse.getInstance();
            if (response.getEntity() != null) {
                httpInfoRes.setBody(EntityUtils.toString(response.getEntity()));
            }
            httpInfoRes.setHttpStatusCode(response.getCode());
            return httpInfoRes;
        } catch (SocketTimeoutException eto) {
            log.error("请求链接超时：{}", eto.getMessage(), eto);
            throw new ApiException("请求超时");
        } catch (Exception e) {
            log.error("executeHttpRequest请求失败：{}", e.getMessage(), e);
            throw new ApiException("请求失败");
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    log.error("CloseableHttpResponse关闭失败：{}", e.getMessage(), e);
                }
            }
        }
    }


    /**
     * 下载文件
     *
     * @param url       请求路径
     * @param reqHeader 请求头
     * @param params    请求参数
     * @return ApiResponseEntity
     */
    public static DssEntityResponse downLoadFiles(String url, Map<String, String> reqHeader, String params) throws Exception {
        DssEntityResponse entity = new DssEntityResponse();

        CloseableHttpResponse response = null;
        // 初始化httpClient
        CloseableHttpClient httpClient = getHttpClient(httpConfig);
        try {
            // 创建http请求 设置请求参数
            HttpPost httpPost = getHttpPost(url, params, Constants.CHARSET_UTF8);
            if (reqHeader != null) {
                for (Entry<String, String> entry : reqHeader.entrySet()) {
                    httpPost.addHeader(entry.getKey(), entry.getValue());
                }
            }
            httpPost.setConfig(getRequestConfig(httpConfig));
            response = httpClient.execute(httpPost);
            reqesutAndResponseLog(httpPost, response);
            entity.setHttpStatusCode(response.getCode());
            HttpEntity respEntity = response.getEntity();
            String contentType = respEntity.getContentType();

            if (contentType.contains(ContentType.APPLICATION_JSON.getMimeType())) {
                entity.setData(EntityUtils.toString(respEntity));
            } else {
                byte[] bytes = EntityUtils.toByteArray(response.getEntity());
                entity.setContent(bytes);
                entity.setContentType(contentType);
            }
        } catch (Exception e) {
            log.error("文件下载失败：{}", e.getMessage(), e);
            throw new RuntimeException("文件下载失败");
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    log.error("CloseableHttpResponse关闭失败：{}", e.getMessage(), e);
                }
            }
        }

        return entity;
    }


    /**
     * 请求响应日志打印
     *
     * @param request  请求
     * @param response 响应
     */
    private static void reqesutAndResponseLog(HttpUriRequest request, HttpResponse response) throws URISyntaxException {
        // 请求url
        URI uri = request.getUri();
        if (uri != null) {
            log.info("request url = [{}]", uri);
        }
        // 获取请求头
        Header[] requestHeaders = request.getHeaders();
        if (requestHeaders != null && requestHeaders.length > 0) {
            log.info("request headers {}", Arrays.stream(requestHeaders).collect(Collectors.toList()));
        }
    }

    /**
     * 设置超时时间和代理
     *
     * @return 请求配置
     */
    private static RequestConfig getRequestConfig(HttpConfig httpConfig) {
        RequestConfig.Builder custom = RequestConfig.custom();
        if (httpConfig != null) {
            if (httpConfig.getReadTimeout() != null) {
                custom.setResponseTimeout(httpConfig.getReadTimeout(), TimeUnit.MILLISECONDS);
            }
            if (httpConfig.getConnectTimeout() != null) {
                custom.setConnectionRequestTimeout(httpConfig.getConnectTimeout(), TimeUnit.MILLISECONDS);
            }
            // 设置代理
            if (Boolean.TRUE.equals(httpConfig.getProxyFlag())) {
                HttpHost proxy = new HttpHost("http", httpConfig.getProxyHost(), httpConfig.getProxyPort());
                custom.setProxy(proxy);
            }
        }
        return custom.build();
    }

    public static class TrustAllTrustManager implements X509TrustManager {
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            try {
                chain[0].checkValidity();
            } catch (Exception e) {
                throw new CertificateException("Certificate not valid or trusted.");
            }
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            try {
                chain[0].checkValidity();
            } catch (Exception e) {
                throw new CertificateException("Certificate not valid or trusted.");
            }
        }
    }
}
